using System;
using System.Collections.Generic;
using System.Text;

namespace RapidHDL.Fabric
{
    public class LogicBlock : Block 
    {
        int iWidth;
        LogicBlockExpression oExpression;

        Dictionary<string, int> dicInputWidths = new Dictionary<string,int>();


        public LogicBlock(string psName, string psExpression)
            : base(psName, "LOGIC")
        {
            oExpression = new LogicBlockExpression();
            if (!oExpression.ParseExpression(psExpression))
                throw new Exception("Logic block expression " + psExpression + " has a syntax error.");
            CheckExpressionForInput(oExpression);
            foreach (string sInput in dicInputWidths.Keys)
            {
                CreateInput(sInput, dicInputWidths[sInput]);
            }
            int iWidth = GetOutputWidth(oExpression);
            CreateOutput("OUT", iWidth);
        }

        private void CheckExpressionForInput(LogicBlockExpression poExpression)
        {
            if (poExpression.InputName != "")
            {
                if (dicInputWidths.ContainsKey(poExpression.InputName))
                {
                    if (dicInputWidths[poExpression.InputName] < poExpression.InputWidth)
                        dicInputWidths[poExpression.InputName] = poExpression.InputWidth;
                }
                else
                {
                    dicInputWidths.Add(poExpression.InputName, poExpression.InputWidth);
                }
            }
            else
            {
                CheckExpressionForInput(poExpression.exExpressionA);
                CheckExpressionForInput(poExpression.exExpressionB);
            }
        }

        private int GetMapExpressionWidth(LogicBlockExpression poExpression)
        {
            Input oInput = dicInputs[poExpression.InputName];
            int iWidth = 0;

            if (poExpression.StartBit != 0)
            {
                iWidth = poExpression.StartBit - poExpression.EndBit;
                if (iWidth < 0)
                    iWidth *= -1;
                iWidth += 1;
                return iWidth;
            }

            return poExpression.InputWidth;
        }

        
        private int GetOutputWidth(LogicBlockExpression poExpression)
        {

            if (poExpression.exExpressionA == null)
            {
                return GetMapExpressionWidth(poExpression);
            }

            int iWidthA = GetOutputWidth(poExpression.exExpressionA);
            int iWidthB = GetOutputWidth(poExpression.exExpressionB);

            string sOperator = poExpression.sOperator;
            int piParamA = 0;
            if (sOperator.Length > 1)
            {
                if (sOperator.Substring(0, 2) == "<<" || sOperator.Substring(0, 2) == ">>")
                {
                    if (sOperator.Length == 2)
                        sOperator += "l";
                    if (sOperator.Length == 3)
                        sOperator += "1";
                    string sShiftWidth = sOperator.Substring(2, sOperator.Length - 3);
                    if (!Conversion.IsNumeric(sShiftWidth))
                        throw new Exception("Shift expression is not numeric... " + poExpression.sExpression);
                    piParamA = Conversion.StringToInt(sShiftWidth);

                }
            }
            ALUType eALUType = ALUComponent.ALUTypeFromString(poExpression.sOperator);
            if (eALUType == ALUType.Merge)
            {
                piParamA = iWidthB;
            }
            int iWidthIn = 0;
            int iWidthOut = 0;
            ALUComponent.SizeIO(eALUType, iWidthA, piParamA, out iWidthIn, out iWidthOut);
            return iWidthOut;
        }










        private NodeVector MapExpressionInput(LogicBlockExpression poExpression)
        {
            Input oInput = dicInputs[poExpression.InputName];
            NodeVector nvInput = oInput.NodeVector;
            NodeVector nvSized = null;
            NodeVector nvOutput = null;

            if (poExpression.StartBit != 0)
            {
                nvSized = nvInput.Subset(ParentComponent,poExpression.StartBit,poExpression.EndBit);
            }
            else
            {
                nvSized = nvInput;
            }
            if (poExpression.sOperator != null && poExpression.sOperator != "")
            {
                if (poExpression.sOperator[0] != 'U')
                    throw new Exception("Operator of logic block is not for single input... " + poExpression.sExpression);
                string sALUName = "alu" + ParentComponent.Components.Count.ToString();
                ALUType eType = ALUComponent.ALUTypeFromString(poExpression.sOperator);
                ALUComponent oALU = new ALUComponent(ParentComponent, sALUName, eType, nvSized.Width);
                oALU.I_A.Join(nvSized);
                nvOutput = ParentComponent.CreateNodeVector(sALUName + "_out", oALU.O_A.Width);
                oALU.O_A.Join(nvOutput);
            }
            else
            {
                nvOutput = nvSized;
            }

            return nvOutput;
        }

        private NodeVector GenerateExpression(LogicBlockExpression poExpression)
        {
            if (poExpression.exExpressionA == null)
            {
                return MapExpressionInput(poExpression);
            }

            NodeVector nvA = GenerateExpression(poExpression.exExpressionA);
            NodeVector nvB = GenerateExpression(poExpression.exExpressionB);

            string sOperator = poExpression.sOperator;
            int piParamA = 0;
            if (sOperator.Length > 1)
            {
                if (sOperator.Substring(0, 2) == "<<" || sOperator.Substring(0, 2) == ">>")
                {
                    if (sOperator.Length == 2)
                        sOperator += "l";
                    if (sOperator.Length == 3)
                        sOperator += "1";
                    string sShiftWidth = sOperator.Substring(2, sOperator.Length - 3);
                    if (!Conversion.IsNumeric(sShiftWidth))
                        throw new Exception("Shift expression is not numeric... " + poExpression.sExpression);
                    piParamA = Conversion.StringToInt(sShiftWidth);

                }
            }
            ALUType eALUType = ALUComponent.ALUTypeFromString(poExpression.sOperator);
            string sALUName = "alu" + ParentComponent.Components.Count.ToString();
            if (eALUType == ALUType.Error)
            {
                throw new Exception("ALU Type for operator " + poExpression.sOperator + " is not recognized.");
            }
 
            if (eALUType == ALUType.Merge)
            {
                piParamA = nvB.Width;
            }
            ALUComponent oALU = new ALUComponent(ParentComponent, sALUName, eALUType, nvA.Width,piParamA);
            oALU.I_A.Join(nvA);
            if (oALU.I_B != null)
                oALU.I_B.Join(nvB);
            NodeVector nvOutput = ParentComponent.CreateNodeVector(sALUName + "_out", oALU.O_A.Width);
            oALU.O_A.Join(nvOutput);
            return nvOutput;
        }

        internal override void GenerateStructure()
        {
            foreach (string sInput in dicInputWidths.Keys)
            {
                NodeVector nvInput = ParentComponent.CreateNodeVector(Name + "_" + sInput + "_I", dicInputWidths[sInput]);
                Inputs[sInput].LinkNodeVector(nvInput);
            }

            NodeVector nvOutput = GenerateExpression(oExpression);
            Outputs["OUT"].LinkNodeVector(nvOutput);
        }


    }

    internal class LogicBlockExpression
    {
        public string sExpression="";
        public string sInputA="";
        public string sInputB="";
        public string sOperator="";
        public LogicBlockExpression exExpressionA=null;
        public LogicBlockExpression exExpressionB=null;

        public int InputWidth = 0;
        public bool UnaryOperator = false;
        public string InputName = "";
        public int StartBit=-1;
        public int EndBit=-1;

        public bool ParseExpression(string psExpression)
        {
            sExpression = psExpression.Trim().ToUpper();
            int iInputALen = FindInputLenA(sExpression);
            while (iInputALen == sExpression.Length && sExpression[0] == '(')
            {
                sExpression = sExpression.Substring(1,sExpression.Length - 2).Trim();
                iInputALen = FindInputLenA(sExpression);
            }
            if (iInputALen < 0)
                return false;
            sInputA = sExpression.Substring(0,iInputALen);
            if (sInputA.Length == sExpression.Length)
            {
                bool bOperator = false;
                switch(sInputA[0])
                {
                    case '!':
                        bOperator = true;
                        break;
                    case '&':
                        bOperator = true;
                        break;
                    case '|':
                        bOperator = true;
                        break;
                    default:
                        bOperator = false;
                        break;
                }

                if (bOperator)
                {
                    sOperator = 'U' + sInputA[0].ToString();
                    sInputA = sInputA.Substring(1,sInputA.Length - 1);
                    UnaryOperator = true;
                }

                if (sInputA.Contains("["))
                {
                    if (sInputA[sInputA.Length - 1] != ']')
                        return false;
                    int iSubPos = sInputA.IndexOf('[');
                    string sSubBits = sInputA.Substring(iSubPos + 1, sInputA.Length - iSubPos - 2);
                    if (sSubBits.Contains(":"))
                    {
                        int iSubSepPos = sSubBits.IndexOf(':');
                        string sLeftBit = sSubBits.Substring(0, iSubSepPos);
                        string sRightBit = sSubBits.Substring(iSubSepPos + 1, sSubBits.Length - iSubSepPos - 1);
                        if (!Conversion.IsNumeric(sLeftBit))
                            return false;
                        if (!Conversion.IsNumeric(sRightBit))
                            return false;
                        StartBit = Conversion.StringToInt(sLeftBit);
                        EndBit = Conversion.StringToInt(sRightBit);
                    }
                    else
                    {
                        if (!Conversion.IsNumeric(sSubBits))
                            return false;
                        StartBit = Conversion.StringToInt(sSubBits);
                        EndBit = Conversion.StringToInt(sSubBits);
                    }
                    sInputA = sInputA.Substring(0, iSubPos);
                }
                else
                {
                    StartBit = 0;
                    EndBit = InputWidth - 1;
                }
                if (sInputA.Contains("<"))
                {
                    if (sInputA[sInputA.Length - 1] != '>')
                        return false;
                    int iSubPos = sInputA.IndexOf('<');
                    string sWidth = sInputA.Substring(iSubPos + 1,sInputA.Length - iSubPos - 2);
                    if (!Conversion.IsNumeric(sWidth))
                        return false;
                    InputWidth = Conversion.StringToInt(sWidth);
                    InputName = sInputA.Substring(0,iSubPos);
                }
                else
                {
                    InputName = sInputA;
                    InputWidth = 1;
                }
                return true;
            }

            int iInputBLen = FindInputLenB(sExpression);
            if (iInputBLen < 0)
                return false;
            if (sExpression.Length < iInputBLen + iInputALen + 1)
                return false;
            sInputB = sExpression.Substring(sExpression.Length - iInputBLen,iInputBLen).Trim();
            sOperator = sExpression.Substring(iInputALen,sExpression.Length - iInputALen - iInputBLen);
            sOperator = sOperator.Trim();
            if (sOperator.Contains(" "))
                return false;
            if (sOperator.Length == 0)
                return false;

             exExpressionA = new LogicBlockExpression();
             if (!exExpressionA.ParseExpression(sInputA))
                return false;
 
            exExpressionB = new LogicBlockExpression();
            if (!exExpressionB.ParseExpression(sInputB))
                return false;

            return true;
        }

        private int FindInputLenA(string psExpression)
        {
            int iParenCount = 0;
            int iIdx = 0;
            
            while (iIdx < sExpression.Length && (iParenCount > 0 || iIdx < 1 || sExpression[iIdx] != ' '))
            {
                if (sExpression[iIdx] == '(')
                    iParenCount++;
                if (sExpression[iIdx] == ')')
                    iParenCount--;
                iIdx++;
            }
            if (iParenCount == 0)
                return iIdx;
            return -1;
        }

        private int FindInputLenB(string psExpression)
        {
            int iParenCount = 0;
            int iIdx = psExpression.Length - 1;

            while (iIdx > 0 && (iParenCount > 0 || iIdx == psExpression.Length || sExpression[iIdx] != ' '))
            {
                if (sExpression[iIdx] == ')')
                    iParenCount++;
                if (sExpression[iIdx] == '(')
                    iParenCount--;
                iIdx--;
            }
            if (iParenCount == 0)
                return sExpression.Length - iIdx;
            return -1;
        }

        
    }

    /*internal class LogicBlockGroup
    {
        Input In1;
        Input In2;
        LogicGroup Input1;
        LogicGroup Input2;
        ALUType eType;
        Output Output;
    }*/
}
